#include "precompile.h"

#include <windows.h>
#include <gdiplus.h>
#pragma comment(lib,"gdiplus.lib")
#include <cmath>
#include "Win32Graphics.h"

Win32Graphics::Win32Graphics()
{
	m_graphics = NULL;
	m_surface =NULL;
}
Win32Graphics::~Win32Graphics()
{
	delete m_graphics;
	delete m_surface;
}

GraphicsSurface* Win32Graphics::GetGraphicsSurface() const
{
	return m_surface;
}

bool Win32Graphics::Create(GraphicsSurface* surface,float scale/*=1.0f*/)
{
	if (m_surface)
		delete m_surface;

	m_surface = surface->Clone();

	switch (surface->GetCategory())
	{
	case GraphicsSurface::imageSurface:
		{
			ImageSurface* imageSurface = (ImageSurface*)surface;
			g2::Image* image = g2::Image::FromFile(imageSurface->image.GetFilename().c_str());
			m_graphics = new g2::Graphics(image);
			break;
		}
	case GraphicsSurface::deviceSurface:
		{
			DeviceSurface* deviceSurface = (DeviceSurface*)surface;
			m_graphics = new g2::Graphics((HDC)deviceSurface->deviceContext);
			break;
		}
	default:
		break;
	}

	m_graphics->SetPageUnit(g2::UnitDocument);

	m_graphics->SetPageScale(scale);
	return m_graphics->GetLastStatus()==g2::Ok;
}

void Win32Graphics::ClearColor(const Color& color)
{
	g2::Color g2color(color.GetA(),color.GetR(),color.GetG(),color.GetB());
	m_graphics->Clear(g2color);
}

void Win32Graphics::SetPageScale(float scale)
{
	m_graphics->SetPageScale(scale);
}

float Win32Graphics::GetPageScale() const
{
	return m_graphics->GetPageScale();
}

float Win32Graphics::GetDpiX() const
{
	return m_graphics->GetDpiX();
}

float Win32Graphics::GetDpiY() const
{
	return m_graphics->GetDpiY();
}

void Win32Graphics::ResetTransform()
{
	m_graphics->ResetTransform();
}

void Win32Graphics::SetTransform(const Matrix& matrix)
{
	float m11,m12,m21,m22,dx,dy;
	matrix.GetElements(m11,m12,m21,m22,dx,dy);

	g2::Matrix g2matrix(m11,m12,m21,m22,dx,dy);
	m_graphics->SetTransform(&g2matrix);
}

void Win32Graphics::MultiplyTransform(const Matrix& matrix,Matrix::matrixOrder order/*=Matrix::prependOrder*/)
{
	float m11,m12,m21,m22,dx,dy;
	matrix.GetElements(m11,m12,m21,m22,dx,dy);

	g2::Matrix g2matrix(m11,m12,m21,m22,dx,dy);
	m_graphics->MultiplyTransform(&g2matrix,Tog2MatrixOrder(order));
}

void Win32Graphics::TranslateTransform(float dx,float dy,Matrix::matrixOrder order/*=Matrix::prependOrder*/)
{
	m_graphics->TranslateTransform(dx,dy,Tog2MatrixOrder(order));
}

void Win32Graphics::ScaleTransform(float sx,float sy,Matrix::matrixOrder order/*=Matrix::prependOrder*/)
{
	m_graphics->ScaleTransform(sx,sy,Tog2MatrixOrder(order));
}

void Win32Graphics::RotateTransform(float angle,Matrix::matrixOrder order/*=Matrix::prependOrder*/)
{
	m_graphics->RotateTransform(angle,Tog2MatrixOrder(order));
}

Matrix Win32Graphics::GetTransform() const
{
	g2::Matrix g2matrix;
	m_graphics->GetTransform(&g2matrix);

	/*
		0(m11)	1(m12)
		2(m21)	3(m22)
		4(dx)	5(dy)
	*/
	g2::REAL elements[6] = {0};
	if (g2matrix.GetElements(&elements[0]) == g2::Ok)
		return Matrix(elements[0],elements[1],elements[2],elements[3],elements[4],elements[5]);;

	return Matrix::Identity();
}

Point Win32Graphics::CoordinateDeviceToWorld(const Point& pt) const
{
	g2::PointF worldpt;
	worldpt.X = pt.x;
	worldpt.Y = pt.y;
	m_graphics->TransformPoints(g2::CoordinateSpaceWorld,g2::CoordinateSpaceDevice,&worldpt,1);
	return Point(worldpt.X,worldpt.Y);
}

Point Win32Graphics::CoordinateWorldToDevice(const Point& pt) const
{
	g2::PointF devicept;
	devicept.X = pt.x;
	devicept.Y = pt.y;
	m_graphics->TransformPoints(g2::CoordinateSpaceDevice,g2::CoordinateSpaceWorld,&devicept,1);
	return Point(devicept.X,devicept.Y);
}

Rect Win32Graphics::CoordinateDeviceToWorld(const Rect& rect) const
{
	return Rect(CoordinateDeviceToWorld(rect.LeftTop()),CoordinateDeviceToWorld(rect.RightBottom()));
}

Rect Win32Graphics::CoordinateWorldToDevice(const Rect& rect) const
{
	return Rect(CoordinateWorldToDevice(rect.LeftTop()),CoordinateWorldToDevice(rect.RightBottom()));
}

Size Win32Graphics::CoordinateDeviceToWorld(const Size& size) const
{
	Rect rc(0,0,size.cx,size.cy);
	CoordinateDeviceToWorld(rc);
	return rc.GetSize();
}

Size Win32Graphics::CoordinateWorldToDevice(const Size& size) const
{
	Point pt(size.cx,size.cy);
	CoordinateWorldToDevice(pt);
	return Size(pt.x,pt.y);
}

float Win32Graphics::UnitMMtoLine(float value)
{
	return GetPageScale()*300.0f*value/25.4f;
}

float Win32Graphics::UnitCMtoLine(float value)
{
	return GetPageScale()*300.0f*value/2.54f;
}

float Win32Graphics::UnitInchtoLine(float value)
{
	return GetPageScale()*300.0f*value;
}

float Win32Graphics::UnitLinetoMM(float value)
{
	return value/300.0f*25.4f;
}

float Win32Graphics::UnitLinetoCM(float value)
{
	return value/300.0f*2.54f;
}

float Win32Graphics::UnitLinetoInch(float value)
{
	return value/300.0f;
}

void Win32Graphics::DrawLine(const Pen& pen,const Point& pt1,const Point& pt2)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	m_graphics->DrawLine(g2pen,pt1.x,pt1.y,pt2.x,pt2.y);
	delete g2pen;
}

void Win32Graphics::DrawLines(const Pen& pen,const Points& pts)
{
	ASSERT(pts.size() > 1u);

	g2::PointF* g2pts = Tog2Points(pts);
	g2::Pen* g2pen = Tog2Pen(pen);
	m_graphics->DrawLines(g2pen,g2pts,pts.size());
	delete g2pen;
	delete[] g2pts;
}

void Win32Graphics::DrawRectangle(const Pen& pen,const Rect& rc)
{
	DrawRectangle(pen,rc.LeftTop(),rc.RightBottom());
}

void Win32Graphics::DrawRectangle(const Pen& pen,const Point& pt1,const Point& pt2)
{
	Point begin = pt2>pt1 ? pt1 : pt2;
	Point end = pt2>pt1 ? pt2 : pt1;
	float width = end.x-begin.x;
	float height = end.y-begin.y;

	g2::Pen* g2pen = Tog2Pen(pen);
	m_graphics->DrawRectangle(g2pen,begin.x,begin.y,width,height);
	delete g2pen;
}

void Win32Graphics::DrawRectangle(const Pen& pen,float x,float y,float width,float height)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	m_graphics->DrawRectangle(g2pen,x,y,width,height);
	delete g2pen;
}

void Win32Graphics::DrawRectangles(const Pen& pen,const Rects& rects)
{
	ASSERT(rects.size() > 0u);

	g2::RectF* g2rects = Tog2Rects(rects);
	g2::Pen* g2pen = Tog2Pen(pen);
	m_graphics->DrawRectangles(g2pen,g2rects,rects.size());
	delete g2pen;
	delete[] g2rects;
}

void Win32Graphics::DrawEllipse(const Pen& pen,const Rect& rc)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	g2::RectF g2rect(rc.X(),rc.Y(),rc.Width(),rc.Height());
	m_graphics->DrawEllipse(g2pen,g2rect);
	delete g2pen;
}

void Win32Graphics::DrawPolygon(const Pen& pen,const Points& pts)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	g2::PointF* g2pts = Tog2Points(pts);
	m_graphics->DrawPolygon(g2pen,g2pts,pts.size());
	delete g2pen;
	delete[] g2pts;
}

void Win32Graphics::DrawArc(const Pen& pen,float x,float y,float width,float height,float startAngle,float sweepAngle)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	m_graphics->DrawArc(g2pen,x,y,width,height,startAngle,sweepAngle);
	delete g2pen;
}

void Win32Graphics::DrawArc(const Pen& pen,const Rect& rc,float startAngle,float sweepAngle)
{
	this->DrawArc(pen,rc.LeftTop().x,rc.LeftTop().y,rc.Width(),rc.Height(),startAngle,sweepAngle);
}

void Win32Graphics::DrawPie(const Pen& pen,const Rect& rc,float startAngle,float sweepAngle)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	m_graphics->DrawPie(g2pen,rc.LeftTop().x,rc.LeftTop().y,rc.Width(),rc.Height(),startAngle,sweepAngle);
	delete g2pen;
}

void Win32Graphics::DrawCurve(const Pen& pen,const Points& pts,bool closed/*=false*/)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	g2::PointF* g2pts = Tog2Points(pts);
	if (!closed)
		m_graphics->DrawCurve(g2pen,g2pts,pts.size());
	else
		m_graphics->DrawClosedCurve(g2pen,g2pts,pts.size());
	delete g2pen;
	delete[] g2pts;
}

void Win32Graphics::DrawCurve(const Pen& pen,const Points& pts,float tension,bool closed/*=false*/)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	g2::PointF* g2pts = Tog2Points(pts);
	if (!closed)
		m_graphics->DrawCurve(g2pen,g2pts,pts.size(),tension);
	else
		m_graphics->DrawClosedCurve(g2pen,g2pts,pts.size(),tension);
	delete g2pen;
	delete[] g2pts;
}

void Win32Graphics::DrawCurve(const Pen& pen,const Points& pts,int offset,int numberOfSegments,float tension/* = 0.5f*/)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	g2::PointF* g2pts = Tog2Points(pts);
	m_graphics->DrawCurve(g2pen,g2pts,pts.size(),offset,numberOfSegments,tension);
	delete g2pen;
	delete[] g2pts;
}

void Win32Graphics::DrawBezier(const Pen& pen,const Point& pt1,const Point& pt2,const Point& pt3,const Point& pt4)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	m_graphics->DrawBezier(g2pen,pt1.x,pt1.y,pt2.x,pt2.y,pt3.x,pt3.y,pt4.x,pt4.y);
	delete g2pen;
}

void Win32Graphics::DrawBeziers(const Pen& pen,const Points& pts)
{
	g2::Pen* g2pen = Tog2Pen(pen);
	g2::PointF* g2pts = Tog2Points(pts);
	m_graphics->DrawBeziers(g2pen,g2pts,pts.size());
	delete g2pen;
	delete[] g2pts;
}

void Win32Graphics::DrawImage(const Image& image,const Rect& rect)
{
	g2::Image g2image(image.GetFilename().c_str());
	m_graphics->DrawImage(&g2image,g2::RectF(rect.X(),rect.Y(),rect.Width(),rect.Height()));
}

void Win32Graphics::FillRectangle(const Brush& brush,const Rect& rect)
{
	this->FillRectangle(brush,rect.LeftTop(),rect.GetSize());
}

void Win32Graphics::FillRectangle(const Brush& brush,float x,float y,float width,float height)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	m_graphics->FillRectangle(g2brush,x,y,width,height);
	delete g2brush;
}

void Win32Graphics::FillRectangle(const Brush& brush,const Point& pt1,const Size& size)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	m_graphics->FillRectangle(g2brush,pt1.x,pt1.y,size.cx,size.cy);
	delete g2brush;
}

void Win32Graphics::FillRectangles(const Brush& brush,const Rects& rects)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	g2::RectF* g2rects = Tog2Rects(rects);
	m_graphics->FillRectangles(g2brush,g2rects,rects.size());
	delete g2brush;
	delete[] g2rects;
}

void Win32Graphics::FillPolygon(const Brush& brush,const Points& pts)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	g2::PointF* g2pts = Tog2Points(pts);
	m_graphics->FillPolygon(g2brush,g2pts,pts.size());
	delete g2brush;
	delete[] g2pts;
}

void Win32Graphics::FillPolygon(const Brush& brush,const Points& pts,fillMode fill)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	g2::PointF* g2pts = Tog2Points(pts);
	m_graphics->FillPolygon(g2brush,g2pts,pts.size(),Tog2FillMode(fill));
	delete g2brush;
	delete[] g2pts;
}

void Win32Graphics::FillEllipse(const Brush& brush,const Rect& rect)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	m_graphics->FillEllipse(g2brush,g2::RectF(rect.X(),rect.Y(),rect.Width(),rect.Height()));
	delete g2brush;
}

void Win32Graphics::FillPie(const Brush& brush,const Rect& rect,float startAngle,float sweepAngle)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	m_graphics->FillPie(g2brush,g2::RectF(rect.X(),rect.Y(),rect.Width(),rect.Height()),startAngle,sweepAngle);
	delete g2brush;
}

void Win32Graphics::FillClosedCurve(const Brush& brush,const Points& pts)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	g2::PointF* g2pts = Tog2Points(pts);
	m_graphics->FillClosedCurve(g2brush,g2pts,pts.size());
	delete[] g2pts;
	delete g2brush;
}

void Win32Graphics::FillClosedCurve(const Brush& brush,const Points& pts,fillMode fill,float tension/*=0.5f*/)
{
	g2::Brush* g2brush = Tog2Brush(brush);
	g2::PointF* g2pts = Tog2Points(pts);
	m_graphics->FillClosedCurve(g2brush,g2pts,pts.size(),Tog2FillMode(fill),tension);
	delete[] g2pts;
	delete g2brush;
}

void Win32Graphics::DrawText(const String& string,const Point& origin)
{
	g2::Font font(L"Arial",10);
	g2::Brush* brush = new g2::SolidBrush(Color::Blue);
	m_graphics->DrawString(string.c_str(),
												   string.length(),
												   &font,
												   g2::PointF(origin.x,origin.y),
												   brush);
	delete brush;
}

//////////////////////////////////////////////////////////////////////////
//private methods
g2::MatrixOrder Win32Graphics::Tog2MatrixOrder(Matrix::matrixOrder order)
{
	if (order == Matrix::appendOrder)
		return g2::MatrixOrderAppend;
	if (order == Matrix::prependOrder)
		return g2::MatrixOrderPrepend;

	ASSERT(FALSE);
	return g2::MatrixOrderPrepend;
}

g2::Color Win32Graphics::Tog2Color(const Color& color)
{
	return g2::Color(color.GetA(),color.GetR(),color.GetG(),color.GetB());
}

g2::Brush* Win32Graphics::Tog2Brush(const Brush& brush)
{
	switch (brush.GetType())
	{
	case solidBrush:
		{
			const SolidBrush& orgbrush = dynamic_cast<const SolidBrush&>(brush);
			return new g2::SolidBrush(Tog2Color(orgbrush.GetColor()));
		}
	case textureBrush:
		break;
	case gradientBrush:
		break;
	case hatchBrush:
		break;
	default:
		break;
	}
	ASSERT(FALSE);
	return NULL;
}

g2::Pen* Win32Graphics::Tog2Pen(const Pen& pen)
{
	g2::Brush* g2brush = Tog2Brush(pen.GetBrush());
	g2::Pen* g2pen = new g2::Pen(g2brush,pen.GetWidth());
	g2pen->SetLineCap(Tog2LineCap(pen.GetStartCap()),
								  Tog2LineCap(pen.GetEndCap()),
								  Tog2DashCap(pen.GetDashCap())
								  );
	delete g2brush;
	return g2pen;
}

g2::DashCap Win32Graphics::Tog2DashCap(dashCap cap)
{
	switch (cap)
	{
	case flatdashCap:
		return g2::DashCapFlat;
	case rounddashCap:
		return g2::DashCapRound;
	case triangledashCap:
		return g2::DashCapTriangle;
	default:
		ASSERT(FALSE);
		break;
	}
	return g2::DashCapFlat;
}

g2::LineCap Win32Graphics::Tog2LineCap(lineCap cap)
{
	switch (cap)
	{
	case flatLineCap:
		return g2::LineCapFlat;
	case roundLineCap:
		return g2::LineCapRound;
	case triangleLineCap:
		return g2::LineCapTriangle;
	case squareAnchorLineCap:
		return g2::LineCapSquareAnchor;
	case roundAnchorLineCap:
		return g2::LineCapRoundAnchor;
	case diamondAnchorLineCap:
		return g2::LineCapDiamondAnchor;
	case arrowAnchorLineCap:
		return g2::LineCapArrowAnchor;
	default:
		ASSERT(FALSE);
		break;
	}
	return g2::LineCapCustom;
}

g2::PointF* Win32Graphics::Tog2Points(const Points& pts)
{
	ASSERT(pts.size() > 0);

	g2::PointF* g2pts = new g2::PointF[pts.size()];
	for (size_t i = 0; i < pts.size(); ++i)
	{
		g2pts[i].X = pts[i].x;
		g2pts[i].Y = pts[i].y;
	}
	return g2pts;
}

g2::RectF* Win32Graphics::Tog2Rects(const Rects& rects)
{
	ASSERT(rects.size() > 0);

	g2::RectF* g2rects = new g2::RectF[rects.size()];
	for (size_t i = 0; i < rects.size(); ++i)
	{
		g2rects[i].X = rects[i].X();
		g2rects[i].Y = rects[i].Y();
		g2rects[i].Width = rects[i].Width();
		g2rects[i].Height = rects[i].Height();
	}
	return g2rects;
}

g2::FillMode Win32Graphics::Tog2FillMode(fillMode fill)
{
	if (fill == alternateFill)
		return g2::FillModeAlternate;
	if (fill == windingFill)
		return g2::FillModeWinding;
	return g2::FillModeAlternate;
}